/* The multi-threaded version of the detector results from discussions (and code
exchanges) with Gary Frost from the Aparapi team
(http://aparapi.googlecode.com)

Aparapi allows Java developers to take advantage of the GPU for some
data-parallel workloads and is released under the modified BSD
license. */

package com.jrelax.plugins.facedetect;

import org.dom4j.Document;
import org.dom4j.io.SAXReader;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class MultiThreadedDetector extends Detector{
	
	public static MultiThreadedDetector create(String filename) {
		/* Read XML file */ 
		Document document=null;

		SAXReader sxb = new SAXReader();
	      try
	      {
	         document = sxb.read(new File(filename));
	      }
	      catch(Exception e){e.printStackTrace();}
	      return new MultiThreadedDetector(document);
	}
	
	/** Detector constructor.
	 * Builds, from a XML document (i.e. the result of parsing an XML file, the corresponding Haar cascade.
	 * @param document The XML document (parsing of file generated by OpenCV) describing the Haar cascade.
	 */
	public MultiThreadedDetector(Document document)
	{
		super(document);
	      }
	
	public List<java.awt.Rectangle> getFaces(BufferedImage image,float baseScale, float scale_inc,float increment, int min_neighbors,final boolean doCannyPruning)
	{
		//StopWatch sw = new StopWatch();
		//sw.start();
			final List<Rectangle> ret=new ArrayList<Rectangle>();
			final int width=image.getWidth();
			final int height=image.getHeight();
			/* Compute the max scale of the detector, i.e. the size of the image divided by the size of the detector. */
			float maxScale = (Math.min((width+0.f)/size.x,(height+0.0f)/size.y));
			
			/* Compute the grayscale image, the integral image and the squared integral image.*/
			final int[][] grayImage=new int[width][height];
			int[][] img = new int[width][height];
			final int[][] squares=new int[width][height];
			for(int i=0;i<width;i++)
			{
				int col=0;
				int col2=0;
				for(int j=0;j<height;j++)
				{
					int c = image.getRGB(i,j);
					int  red = (c & 0x00ff0000) >> 16;
					int  green = (c & 0x0000ff00) >> 8;
					int  blue = c & 0x000000ff;
					int value=(30*red +59*green +11*blue)/100;
					img[i][j]=value;
					grayImage[i][j]=(i>0?grayImage[i-1][j]:0)+col+value;
					squares[i][j]=(i>0?squares[i-1][j]:0)+col2+value*value;
					col+=value;
					col2+=value*value;
				}
			}
			
			/* Eventually compute the gradient of the image, if option is on. */
			int[][] canny = null;
			if(doCannyPruning)
				canny = CannyPruner.getIntegralCanny(img);
			final int[][] canny_final = canny;
			/*Heart of the algorithm : detection */
			ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
			/*For each scale of the detection window */
			for(float scale=baseScale;scale<maxScale;scale*=scale_inc)
			{
				/*Compute the sliding step of the window*/
				int step=(int) (scale*size.x*increment);
				int size=(int) (scale*this.size.x);
				/*For each position of the window on the image, check whether the object is detected there.*/
				for(int i=0;i<width-size;i+=step)
				{
					final int i_final = i;
                    final float scale_final = scale;
                    final int size_final = size;
                    final int step_final = step;
                    Runnable r = new Runnable(){
                        public void run() {
					for(int j=0;j<height-size_final;j+=step_final)
					{
						
						/* If Canny pruning is on, compute the edge density of the zone.
						 * If it is too low, the object should not be there so skip the region.*/
						if(doCannyPruning)
						{
						int edges_density = canny_final[i_final+size_final][j+size_final]+canny_final[i_final][j]-canny_final[i_final][j+size_final]-canny_final[i_final+size_final][j];
						int d = edges_density/size_final/size_final;
						if(d<20||d>100)
							continue;
						}
                        final int j_final = j;
						
	                              boolean pass = true;
	                              /* Perform each stage of the detector on the window. If one stage fails, the zone is rejected.*/
	                              for (Stage s : stages) {
	                                 if (!s.pass(grayImage, squares, i_final, j_final, scale_final)) {
	                                    pass = false;
	                                    //  System.out.println("Failed at Stage " + k);
	                                    break;
	                                 }
	                              }
	      						/* If the window passed all stages, add it to the results. */
	                              if (pass) {
	                                 synchronized (ret) {
	                                    ret.add(new Rectangle(i_final, j_final, size_final, size_final));
	                                 }
	                              }
	                           }
    					}
	                        };
	                        threadPool.execute(r);
				}
			}
			threadPool.shutdown();
			try {
				threadPool.awaitTermination(60, TimeUnit.SECONDS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//sw.print("Multi threaded : ");
			return merge(ret,min_neighbors);
	}
}